#include "networkServer.h"
#include "eventServer.h"
#include "fileServer.h"
#include "object.h"

#include <cstdio>
#include <cstring>

namespace sde {
    
    NetworkServer::NetworkServer() :
        NetworkNode(false),
        connected(false),
        server(0) {
        EventServer::getSingleton().subscribe("update",boost::bind(&NetworkServer::update,this,_1));
    }
    
    void NetworkServer::connectClient() {
        connect("localhost",1234);
        
        printf("trying to connect...\n");
        pump(1000);
        
        if (!connected)
            printf("couldn't find server, running in standalone mode\n");
    }
    
    void NetworkServer::disconnectClient() {
        if (connected)
            disconnect();
    }
    
    void NetworkServer::update(VarMap args) {
        if (connected)
            pump(1);
    }
    
    void NetworkServer::receive(ENetPeer* sender, ENetPacket* packet) {
        int pos = 0;
        unsigned char cmd = readChar(packet,pos);
        switch (cmd) {
        case pingCmd:
            printf("got ping\n");
            break;
            
        case initStateCmd:
            receiveInitState(sender,packet,pos);
            break;
            
        case objectNameCmd:
            receiveObjectName(sender,packet,pos);
            break;
            
        case setVarCmd:
        case distSetVarCmd:
            receiveSetVar(sender,packet,pos);
            break;
            
        case destroyObjectCmd:
            receiveDestroyObject(sender,packet,pos);
            break;
            
        case makeObjectCmd:
            receiveMakeObject(sender,packet,pos);
            break;
            
        case actionNameCmd:
            receiveActionName(sender,packet,pos);
            break;
            
        case runActionCmd:
            receiveRunAction(sender,packet,pos);
            break;
            
        case rpcCmd:
            receiveRpc(sender,packet,pos);
            break;
            
        case rpcResultCmd:
            receiveRpcResult(sender,packet,pos);
            break;
            
        case registerSetVarCmd:
            receiveRegisterSetVar(sender,packet,pos);
            break;
        }
    }
    
    void NetworkServer::onConnected(ENetPeer* peer) {
        printf("connected to server\n");
        
        server = peer;
        
        connected = true;
        
        ENetPacket* packet = createPacket();
        writeChar(packet,modulesCmd);
        
        FileServer& fileServer = FileServer::getSingleton();
        writeInt(packet,fileServer.modules.size());
        for (StringList::iterator i = fileServer.modules.begin(); i != fileServer.modules.end(); ++i)
            writeString(packet,*i);
        
        send(peer,packet);
    }
    
    void NetworkServer::onDisconnected(ENetPeer* peer) {
        printf("disconnected from server\n");
        
        connected = false;
        Object::shutdown = true;
    }
    
    void NetworkServer::receiveInitState(ENetPeer* peer, ENetPacket* packet, int& pos) {
        int numObjects = readInt(packet,pos);
        printf("got %d objects\n",numObjects);
        for (int i = 0; i < numObjects; ++i) {
            std::string name = readString(packet,pos);
            VarMap vars = readVarMap(packet,pos);
            
            Object *obj = new Object(name,true);
            obj->updateVars(vars,false);
        }
    }
    
    void NetworkServer::receiveObjectName(ENetPeer* peer, ENetPacket* packet, int& pos) {
        resultName = readString(packet,pos);
    }
    
    std::string NetworkServer::doMakeObjectName(std::string desiredName) {
        std::ostringstream sout;
        int i = 1;
        do {
            sout.str("");
            sout << desiredName;
            if (i > 1)
                sout << "_" << i;
            i++;
        } while (Object::hasObject(sout.str()));
        return sout.str();
    }
    
    std::string NetworkServer::makeObjectName(std::string desiredName) {
        if (connected) {
            resultName = "";
            ENetPacket* packet = createPacket();
            writeChar(packet,makeObjectNameCmd);
            writeString(packet,desiredName);
            send(server,packet);
            
            while (resultName == "")
                pump(0);
                
            return resultName;
        } else {
            return doMakeObjectName(desiredName);
        }
    }
    
    void NetworkServer::setVar(std::string objectName, std::string varName, Var val) {
        if (connected) {
            std::string varId = objectName + "." + varName;
            bool varRegistered = registeredSetVars.find(varId) != registeredSetVars.end();
            bool varRequested = requestedSetVars.find(varId) != requestedSetVars.end();
            
            if (!varRegistered) {
                printf("register %s\n",varId.c_str());
                registeredSetVars[varId] = true;
                
                ENetPacket* registerPacket = createPacket();
                writeChar(registerPacket,registerSetVarCmd);
                writeString(registerPacket,varId);
                send(server,registerPacket);
                
                ENetPacket* setVarPacket = createPacket();
                writeChar(setVarPacket,setVarCmd);
                writeString(setVarPacket,objectName);
                writeString(setVarPacket,varName);
                writeVar(setVarPacket,val);
                send(server,setVarPacket);
                
            } else if (varRequested) {
                ENetPacket* setVarPacket = createPacket();
                writeChar(setVarPacket,setVarCmd);
                writeString(setVarPacket,objectName);
                writeString(setVarPacket,varName);
                writeVar(setVarPacket,val);
                send(server,setVarPacket);
            } else {
//                 printf("var denied %s\n",varId.c_str());
            }
        }
    }
    
    void NetworkServer::distSetVar(std::string objectName, std::string varName, Var val) {
        if (connected) {
            ENetPacket* packet = createPacket();
            writeChar(packet,distSetVarCmd);
            writeString(packet,objectName);
            writeString(packet,varName);
            writeVar(packet,val);
            send(server,packet);
        }
    }
    
    void NetworkServer::requestVar(std::string objectName, std::string varName) {
        if (connected) {
            ENetPacket* packet = createPacket();
            writeChar(packet,requestVarCmd);
            writeString(packet,objectName);
            writeString(packet,varName);
            send(server,packet);
        }
    }
    
    void NetworkServer::receiveSetVar(ENetPeer* peer, ENetPacket* packet, int& pos) {
        std::string objectName = readString(packet,pos);
        std::string varName = readString(packet,pos);
        Var val = readVar(packet,pos);
        
//         printf("receiveSetVar %s %s\n",objectName.c_str(),varName.c_str());
        
        Object* obj = Object::getObject(objectName);
        if (obj) {
            obj->setVar(varName,val,false);
        } else
            printf("ERROR: can't set var %s in unknown object %s\n",varName.c_str(),objectName.c_str());
    }
    
    void NetworkServer::destroyObject(std::string objectName) {
        if (connected) {
            ENetPacket* packet = createPacket();
            writeChar(packet,destroyObjectCmd);
            writeString(packet,objectName);
            send(server,packet);
        }
    }
    
    void NetworkServer::receiveDestroyObject(ENetPeer* peer, ENetPacket* packet, int& pos) {
        std::string objectName = readString(packet,pos);
        Object::deleteObject(objectName,false);
    }
    
    void NetworkServer::receiveMakeObject(ENetPeer* peer, ENetPacket* packet, int& pos) {
        std::string objectName = readString(packet,pos);
        new Object(objectName,true);
    }
    
    std::string NetworkServer::runAction(std::string objectName, std::string actionType, std::string actionName) {
        if (connected) {
            resultName = "";
            
            ENetPacket* packet = createPacket();
            writeChar(packet,runActionCmd);
            writeString(packet,objectName);
            writeString(packet,actionType);
            writeString(packet,actionName);
            send(server,packet);
            
            while (resultName == "") pump(0);
            
            return resultName;
            
        } else {
        
            Object* object = Object::getObject(objectName);
            if (object) {
                
                std::ostringstream sout;
                int i = 1;
                do {
                    sout.str("");
                    sout << actionName;
                    if (i > 1)
                        sout << "_" << i;
                    i++;
                } while (object->hasAction(sout.str()));
                
                std::string realActionName = sout.str();
                object->doRunAction(actionType,realActionName);
                return realActionName;
                
            } else {
                printf("ERROR: trying to run action %s for unknown object %s in standalone mode",
                    actionType.c_str(),objectName.c_str());
                return "";
            }
        }
    }
    
    void NetworkServer::receiveActionName(ENetPeer* peer, ENetPacket* packet, int& pos) {
        resultName = readString(packet,pos);
    }
    
    void NetworkServer::receiveRunAction(ENetPeer* peer, ENetPacket* packet, int& pos) {
        std::string objectName = readString(packet,pos);
        std::string actionType = readString(packet,pos);
        std::string actionName = readString(packet,pos);
        
        Object* obj = Object::getObject(objectName);
        if (obj) {
            obj->doRunAction(actionType,actionName);
        } else
            printf("ERROR: can't run action %s in unknown object %s\n",actionType.c_str(),objectName.c_str());
    }
    
    void NetworkServer::subscribeRpc(std::string objectName, std::string cmd) {
        ENetPacket* packet = createPacket();
        writeChar(packet,subscribeRpcCmd);
        writeString(packet,objectName);
        writeString(packet,cmd);
        send(server,packet);
    }
    
    void NetworkServer::requestRpc(std::string objectName, std::string cmd, VarMap args) {
        std::string rpcId = objectName + "." + cmd;
        if (rpcResults.find(rpcId) == rpcResults.end())
            rpcResults[rpcId] = VarList();
        
        if (connected && !Object::rpcIsLocal(objectName,cmd)) {
            ENetPacket* packet = createPacket();
            writeChar(packet,rpcCmd);
            writeString(packet,objectName);
            writeString(packet,cmd);
            writeVarMap(packet,args);
            send(server,packet);
        } else {
            Object* object = Object::getObject(objectName);
            Var result;
            if (object)
                result = object->doCallRpc(cmd,args);
            else
                printf("ERROR: can't run local rpc %s in unknown object %s\n",
                    cmd.c_str(),objectName.c_str());
            rpcResults[rpcId].push_back(result);
        }
    }
    
    Var NetworkServer::finishRpc(std::string objectName, std::string cmd) {
        std::string rpcId = objectName + "." + cmd;
        
        while (rpcResults[rpcId].empty())
            pump(0);
            
        Var result = *rpcResults[rpcId].begin();
        rpcResults[rpcId].pop_front();
        return result;
    }
    
    void NetworkServer::receiveRpc(ENetPeer* peer, ENetPacket* packet, int& pos) {
        std::string objectName = readString(packet,pos);
        std::string cmd = readString(packet,pos);
        VarMap args = readVarMap(packet,pos);
        
        Object* object = Object::getObject(objectName);
        Var result;
        if (object) {
            result = object->doCallRpc(cmd,args);
        } else {
            printf("ERROR: can't run rpc %s on unknown object %s\n",cmd.c_str(),objectName.c_str());
        }
        
        ENetPacket* resultPacket = createPacket();
        writeChar(resultPacket,rpcResultCmd);
        writeString(resultPacket,objectName);
        writeString(resultPacket,cmd);
        writeVar(resultPacket,result);
        send(server,resultPacket);
    }
    
    void NetworkServer::receiveRpcResult(ENetPeer* peer, ENetPacket* packet, int& pos) {
        std::string rpcId = readString(packet,pos) + "." + readString(packet,pos);
        Var result = readVar(packet,pos);
        
        rpcResults[rpcId].push_back(result);
    }
    
    void NetworkServer::receiveRegisterSetVar(ENetPeer* peer, ENetPacket* packet, int& pos) {
        std::string varId = readString(packet,pos);
        
        printf("server wants %s\n",varId.c_str());
        
        requestedSetVars[varId] = true;
    }
}
